package org.bookshare.document.navigation;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.benetech.beans.RelativeText;
import org.benetech.collections.IdTree;
import org.benetech.collections.Tree;
import org.benetech.event.EventListener;
import org.benetech.ref.XML;
import org.benetech.util.XMLParseUtils;
import org.bookshare.document.navigation.NavigationElement.NavigationElementType;
import org.bookshare.document.navigation.chapters.Chapter;
import org.bookshare.document.navigation.chapters.ChapterFinder;
import org.bookshare.document.navigation.chapters.Chapters;
import org.jdom.Element;

/**
 * Factory for creating and manipulating NavigationElements.
 * @author Reuben Firmin
 */
public final class NavigationElementFactory {

	/**
	 * Text when we can't find anything near to the navigation element.
	 */
	public static final String BLANK = "______";
	/**
	 * How many characters to show in navigation text.
	 */
	private static final int TEXT_SIZE = 25;

	/**
	 * Non constructor.
	 */
	private NavigationElementFactory() {
	}

	/**
	 * Given a frontmatter and bodymatter, return a list of navigationelements representing chapters.
	 * @param frontMatter Part of the document before the book content
	 * @param bodyMatter The book content
	 * @param totalPages the totalNumber of Pages detected
	 * @param listener User feedback
	 * @return Null if none detected
	 * @see ChapterFinder
	 */
	public static List<NavigationElement> getChapters(final IdTree<Element, String, String> frontMatter,
			final IdTree<Element, String, String> bodyMatter, final int totalPages, final EventListener listener)
	{
		final Chapters chapters = new ChapterFinder().getChapters(frontMatter, bodyMatter, totalPages, listener);
		if (chapters == null) {
			return null;
		}
		// java typing really bites. i should be able to return list as a List<NavigationElement> without reallocating
		// and copying.
		final List<NavigationElement> nav = new ArrayList<NavigationElement>();
		boolean needText = false;
		for (Chapter chapter : chapters) {
			nav.add(chapter);
			if (chapter.getText() == null) {
				needText = true;
				listener.message("Chapter is missing text; will add");
			}
		}
		if (needText) {
			applyNearestText(nav, bodyMatter, listener);
		}
		return nav;
	}

	/**
	 * Convert a list of IdTree nodes (pointers into the document) to a list of navigation elements, including text
	 * that can be displayed to the user.
	 * @param filteredElements The nodes
	 * @param type The type of navigation this is
	 * @param containingTree The tree that these nodes are all part of
	 * @param listener User feedback
	 * @return Never null
	 */
	public static List<NavigationElement> convert(final List<IdTree<Element, String, String>> filteredElements,
			final NavigationElementType type, final IdTree<Element, String, String> containingTree,
			final EventListener listener)
	{
		final List<NavigationElement> navList = new LinkedList<NavigationElement>();
		for (IdTree<Element, String, String> element : filteredElements) {
			navList.add(new GenericNavigationElement(element, type));
		}
		applyNearestText(navList, containingTree, listener);
		return navList;
	}

	/**
	 * Convert this node into a navigation element, including text that can be displayed to the user.
	 * @param node The node to convert
	 * @param type The type of navigation this is
	 * @param containingTree The tree containing this node
	 * @param listener User feedback
	 * @return Never null
	 */
	public static NavigationElement convert(final IdTree<Element, String, String> node,
			final NavigationElementType type, final IdTree<Element, String, String> containingTree,
			final EventListener listener)
	{
		final NavigationElement element = new GenericNavigationElement(node, type);
		final RelativeText text = XMLParseUtils.getNearestText(element.getElement(), containingTree, null);
		if (text == null) {
			throw new RuntimeException("Node: " + node.getContent().toString() + " is not in tree(1)!");
		} else {
    		element.setText(StringUtils.abbreviate(text.getText(), TEXT_SIZE));
		}
		return element;
	}

	/**
	 * Apply the nearest text in the document to this list of navigation elements. Text will be looked for backwards
	 * and forwards for the first in the list, and then in the same direction for the subsequent elements. Pagenum
	 * elements are ignored in the search for text.
	 * @param navigation The list of navigation elements
	 * @param containingTree The tree containing these elements
	 * @param listener User feedback
	 */
	public static void applyNearestText(final List<NavigationElement> navigation,
			final Tree<Element, ? , ? > containingTree, final EventListener listener)
	{
		Boolean direction = null;
		for (NavigationElement element : navigation) {
			final RelativeText text = XMLParseUtils.getNearestText(element.getElement(), containingTree, direction,
					XML.Regex.PAGENUM_PATTERN);
			if (text == null) {
				throw new RuntimeException("Node: " + element.getElement().toString() + " is not in tree(2)!");
    		} else {
    			if (direction == null) {
    				direction = text.isForward();
    			}
	    		element.setText(StringUtils.abbreviate(text.getText(), TEXT_SIZE));
    		}
		}
	}
}
